/*------------------------------------------------------------------------/
/  Universal string handler for user console interface
/-------------------------------------------------------------------------/
/
/  Copyright (C) 2011, ChaN, all right reserved.
/
/ * This software is a free software and there is NO WARRANTY.
/ * No restriction on use. You can use, modify and redistribute it for
/   personal, non-profit or commercial products UNDER YOUR RESPONSIBILITY.
/ * Redistributions of source code must retain the above copyright notice.
/
/-------------------------------------------------------------------------*/

#include "common/xprintf/xprintf.h"


#if _USE_XFUNC_OUT
#include <stdarg.h>
void (*xfunc_out)(unsigned char);   /* Pointer to the output stream */
static char *outptr;

/*----------------------------------------------*/
/* Put a character                              */
/*----------------------------------------------*/

void xputc(char c)
{
    if (_CR_CRLF && c == '\n') { xputc('\r'); }     /* CR -> CRLF */

    if (outptr)
    {
        *outptr++ = (unsigned char)c;
        return;
    }

    if (xfunc_out) { xfunc_out((unsigned char)c); }
}



/*----------------------------------------------*/
/* Put a null-terminated string                 */
/*----------------------------------------------*/

void xputs(      /* Put a string to the default device */
    const char *str             /* Pointer to the string */
)
{
    while (*str)
    {
        xputc(*str++);
    }
}


void xfputs(      /* Put a string to the specified device */
    void(*func)(unsigned char), /* Pointer to the output function */
    const char *str             /* Pointer to the string */
)
{
    void (*pf)(unsigned char);


    pf = xfunc_out;     /* Save current output device */
    xfunc_out = func;   /* Switch output to specified device */

    while (*str)        /* Put the string */
    {
        xputc(*str++);
    }

    xfunc_out = pf;     /* Restore output device */
}



/*----------------------------------------------*/
/* Formatted string output                      */
/*----------------------------------------------*/
/*  xprintf("%d", 1234);            "1234"
    xprintf("%6d,%3d%%", -200, 5);  "  -200,  5%"
    xprintf("%-6u", 100);           "100   "
    xprintf("%ld", 12345678L);      "12345678"
    xprintf("%04x", 0xA3);          "00a3"
    xprintf("%08LX", 0x123ABC);     "00123ABC"
    xprintf("%016b", 0x550F);       "0101010100001111"
    xprintf("%s", "String");        "String"
    xprintf("%-4s", "abc");         "abc "
    xprintf("%4s", "abc");          " abc"
    xprintf("%c", 'a');             "a"
    xprintf("%f", 10.0);            <xprintf lacks floating point support>
*/

void xvprintf(
    const char *fmt,    /* Pointer to the format string */
    va_list arp         /* Pointer to arguments */
)
{
    unsigned int r, i, j, w, f;
    unsigned long v;
    char s[16], c, d, *p;


    for (;;)
    {
        c = *fmt++;                 /* Get a char */

        if (!c) { break; }              /* End of format? */

        if (c != '%')               /* Pass through it if not a % sequense */
        {
            xputc(c);
            continue;
        }

        f = 0;
        c = *fmt++;                 /* Get first char of the sequense */

        if (c == '0')               /* Flag: '0' padded */
        {
            f = 1;
            c = *fmt++;
        }
        else
        {
            if (c == '-')           /* Flag: left justified */
            {
                f = 2;
                c = *fmt++;
            }
        }

        for (w = 0; c >= '0' && c <= '9'; c = *fmt++)   /* Minimum width */
        {
            w = w * 10 + c - '0';
        }

        if (c == 'l' || c == 'L')   /* Prefix: Size is long int */
        {
            f |= 4;
            c = *fmt++;
        }

        if (!c) { break; }              /* End of format? */

        d = c;

        if (d >= 'a') { d -= 0x20; }

        switch (d)                  /* Type is... */
        {
        case 'S' :                  /* String */
            p = va_arg(arp, char *);

            for (j = 0; p[j]; j++) ;

            while (!(f & 2) && j++ < w) { xputc(' '); }

            xputs(p);

            while (j++ < w) { xputc(' '); }

            continue;

        case 'C' :                  /* Character */
            xputc((char)va_arg(arp, int));
            continue;

        case 'B' :                  /* Binary */
            r = 2;
            break;

        case 'O' :                  /* Octal */
            r = 8;
            break;

        case 'D' :                  /* Signed decimal */
        case 'U' :                  /* Unsigned decimal */
            r = 10;
            break;

        case 'X' :                  /* Hexdecimal */
            r = 16;
            break;

        default:                    /* Unknown type (passthrough) */
            xputc(c);
            continue;
        }

        /* Get an argument and put it in numeral */
        v = (f & 4) ? va_arg(arp, long) : ((d == 'D') ? (long)va_arg(arp, int) : (long)va_arg(arp, unsigned int));

        if (d == 'D' && (v & 0x80000000))
        {
            v = 0 - v;
            f |= 8;
        }

        i = 0;

        do
        {
            d = (char)(v % r);
            v /= r;

            if (d > 9) { d += (c == 'x') ? 0x27 : 0x07; }

            s[i++] = d + '0';
        }
        while (v && i < sizeof(s));

        if ((f & 8) && (i < sizeof(s))) { s[i++] = '-'; }

        j = i;
        d = (f & 1) ? '0' : ' ';

        while (!(f & 2) && j++ < w) { xputc(d); }

        do { xputc(s[--i]); }
        while (i);

        while (j++ < w) { xputc(' '); }
    }
}


void xprintf(    /* Put a formatted string to the default device */
    const char *fmt,    /* Pointer to the format string */
    ...                 /* Optional arguments */
)
{
    va_list arp;


    va_start(arp, fmt);
    xvprintf(fmt, arp);
    va_end(arp);
}


void xsprintf(    /* Put a formatted string to the memory */
    char *buff,         /* Pointer to the output buffer */
    const char *fmt,    /* Pointer to the format string */
    ...                 /* Optional arguments */
)
{
    va_list arp;


    outptr = buff;      /* Switch destination for memory */

    va_start(arp, fmt);
    xvprintf(fmt, arp);
    va_end(arp);

    *outptr = 0;        /* Terminate output string with a \0 */
    outptr = 0;         /* Switch destination for device */
}


void xfprintf(      /* Put a formatted string to the specified device */
    void(*func)(unsigned char), /* Pointer to the output function */
    const char *fmt,            /* Pointer to the format string */
    ...                         /* Optional arguments */
)
{
    va_list arp;
    void (*pf)(unsigned char);


    pf = xfunc_out;     /* Save current output device */
    xfunc_out = func;   /* Switch output to specified device */

    va_start(arp, fmt);
    xvprintf(fmt, arp);
    va_end(arp);

    xfunc_out = pf;     /* Restore output device */
}



/*----------------------------------------------*/
/* Dump a line of binary dump                   */
/*----------------------------------------------*/

void put_dump(
    const void *buff,       /* Pointer to the array to be dumped */
    unsigned long addr,     /* Heading address value */
    int len,                /* Number of items to be dumped */
    int width               /* Size of the items (DF_CHAR, DF_SHORT, DF_LONG) */
)
{
    int i;
    const unsigned char *bp;
    const unsigned short *sp;
    const unsigned long *lp;


    xprintf("%08lX ", addr);        /* address */

    switch (width)
    {
    case DW_CHAR:
        bp = buff;

        for (i = 0; i < len; i++)       /* Hexdecimal dump */
        {
            xprintf(" %02X", bp[i]);
        }

        xputc(' ');

        for (i = 0; i < len; i++)       /* ASCII dump */
        {
            xputc((bp[i] >= ' ' && bp[i] <= '~') ? bp[i] : '.');
        }

        break;

    case DW_SHORT:
        sp = buff;

        do                              /* Hexdecimal dump */
        {
            xprintf(" %04X", *sp++);
        }
        while (--len);

        break;

    case DW_LONG:
        lp = buff;

        do                              /* Hexdecimal dump */
        {
            xprintf(" %08LX", *lp++);
        }
        while (--len);

        break;
    }

    xputc('\n');
}

#endif /* _USE_XFUNC_OUT */



#if _USE_XFUNC_IN
unsigned char (*xfunc_in)(void);    /* Pointer to the input stream */

/*----------------------------------------------*/
/* Get a line from the input                    */
/*----------------------------------------------*/

int xgets(   /* 0:End of stream, 1:A line arrived */
    char *buff, /* Pointer to the buffer */
    int len     /* Buffer length */
)
{
    int c, i;


    if (!xfunc_in) { return 0; }        /* No input function specified */

    i = 0;

    for (;;)
    {
        c = xfunc_in();             /* Get a char from the incoming stream */

        if (!c) { return 0; }           /* End of stream? */

        if (c == '\r') { break; }       /* End of line? */

        if (c == '\b' && i)         /* Back space? */
        {
            i--;

            if (_LINE_ECHO) { xputc(c); }

            continue;
        }

        if (c >= ' ' && i < len - 1)    /* Visible chars */
        {
            buff[i++] = c;

            if (_LINE_ECHO) { xputc(c); }
        }
    }

    buff[i] = 0;    /* Terminate with a \0 */

    if (_LINE_ECHO) { xputc('\n'); }

    return 1;
}


int xfgets(  /* 0:End of stream, 1:A line arrived */
    unsigned char (*func)(void),    /* Pointer to the input stream function */
    char *buff, /* Pointer to the buffer */
    int len     /* Buffer length */
)
{
    unsigned char (*pf)(void);
    int n;


    pf = xfunc_in;          /* Save current input device */
    xfunc_in = func;        /* Switch input to specified device */
    n = xgets(buff, len);   /* Get a line */
    xfunc_in = pf;          /* Restore input device */

    return n;
}


/*----------------------------------------------*/
/* Get a value of the string                    */
/*----------------------------------------------*/
/*  "123 -5   0x3ff 0b1111 0377  w "
        ^                           1st call returns 123 and next ptr
           ^                        2nd call returns -5 and next ptr
                   ^                3rd call returns 1023 and next ptr
                          ^         4th call returns 15 and next ptr
                               ^    5th call returns 255 and next ptr
                                  ^ 6th call fails and returns 0
*/

int xatoi(    /* 0:Failed, 1:Successful */
    char **str,     /* Pointer to pointer to the string */
    long *res       /* Pointer to the valiable to store the value */
)
{
    unsigned long val;
    unsigned char c, r, s = 0;


    *res = 0;

    while ((c = **str) == ' ') { (*str)++; }    /* Skip leading spaces */

    if (c == '-')       /* negative? */
    {
        s = 1;
        c = *(++(*str));
    }

    if (c == '0')
    {
        c = *(++(*str));

        switch (c)
        {
        case 'x':       /* hexdecimal */
            r = 16;
            c = *(++(*str));
            break;

        case 'b':       /* binary */
            r = 2;
            c = *(++(*str));
            break;

        default:
            if (c <= ' ') { return 1; } /* single zero */

            if (c < '0' || c > '9') { return 0; }   /* invalid char */

            r = 8;      /* octal */
        }
    }
    else
    {
        if (c < '0' || c > '9') { return 0; }   /* EOL or invalid char */

        r = 10;         /* decimal */
    }

    val = 0;

    while (c > ' ')
    {
        if (c >= 'a') { c -= 0x20; }

        c -= '0';

        if (c >= 17)
        {
            c -= 7;
        }

        if (c >= r) { return 0; }       /* invalid char for current radix */

        val = val * r + c;
        c = *(++(*str));
    }

    if (s) { val = 0 - val; }           /* apply sign if needed */

    *res = val;
    return 1;
}

#endif /* _USE_XFUNC_IN */
